from __future__ import annotations

import re
from functools import cache
from typing import TYPE_CHECKING, Any

import papis.logging

if TYPE_CHECKING:
    from papis.document import KeyConversionPair

logger = papis.logging.get_logger(__name__)

# NOTE: general API information can be found at
#   https://dblp.org/faq/How+to+use+the+dblp+search+API.html
DBLP_API_ENDPOINTS = {
    "publ": "https://dblp.org/search/publ/api",
    "author": "https://dblp.org/search/author/api",
    "venue": "https://dblp.org/search/venue/api",
}
DBLP_URL_FORMAT = "https://dblp.org/rec/{uri}.html"
DBLP_BIB_FORMAT = "https://dblp.org/rec/{uri}.bib"

DBLP_FORMATS = frozenset(["xml", "json", "jsonp"])

# NOTE: caps due to bandwidth reasons
DBLP_MAX_RESULTS = 1000
DBLP_MAX_COMPLETIONS = 1000

# https://dblp.org/faq/What+types+does+dblp+use+for+publication+entries.html
DBLP_TYPE_TO_BIBTEX = {
    "Books and Theses": "book",
    "Journal Articles": "article",
    "Conference and Workshop Papers": "inproceedings",
    "Parts in Books or Collections": "inbook",
    "Editorship": "book",
    "Reference Works": "reference",
    "Data and Artifacts": "dataset",
    "Informal or Other Publications": "report",
}


def _dblp_journal(name: str) -> str | None:
    import json
    result = json.loads(search(query=f"{name}$", api="venue"))

    hits = result["result"]["hits"].get("hit")
    if hits is None or len(hits) != 1:
        return None

    journal = hits[0]["info"]["venue"]
    return str(journal) if journal else None


def _dblp_authors(entries: dict[str, Any]) -> list[dict[str, Any]]:
    from papis.document import split_author_name
    return [split_author_name(author["text"]) for author in entries["author"]]


@cache
def _get_dblp_key_conversion() -> list[KeyConversionPair]:
    from papis.document import KeyConversionPair

    return [
        KeyConversionPair("title", [{"key": "title", "action": None}]),
        KeyConversionPair("volume", [{"key": "volume", "action": None}]),
        KeyConversionPair("number", [{"key": "number", "action": None}]),
        KeyConversionPair("pages", [{"key": "pages", "action": None}]),
        KeyConversionPair("year", [{"key": "year", "action": int}]),
        KeyConversionPair("doi", [{"key": "doi", "action": None}]),
        KeyConversionPair("url", [{"key": "url", "action": None}]),
        KeyConversionPair("type", [{"key": "type", "action": DBLP_TYPE_TO_BIBTEX.get}]),
        KeyConversionPair("venue", [
            {"key": "journal", "action": _dblp_journal},
        ]),
        KeyConversionPair("authors", [
            {"key": "author_list", "action": _dblp_authors},
        ]),
    ]


def search(
        query: str = "",
        output_format: str = "json",
        max_results: int = 30,
        max_completions: int = 10,
        api: str = "publ",
        ) -> str:
    """Query one of the DBLP APIs.

    :param query: a DBLP compatible query string (see the
        `documentation <https://dblp.org/faq/1474589.html>`__ for details).
    :param format: format of the query response (should be one of
        ``"xml"``, ``"json"`` or ``"jsonp"``).
    :param max_results: maximum number of returned results
        (smaller than 1000).
    :param max_completions: maximum number of completions for the query
        string (considers prefix matching).
    :param api: endpoint for the DBLP API to be used (one of ``"publ"``,
        ``"author"`` or ``"venue"``).

    :result: the query response in the requested format.
    """
    if not (0 < max_results <= DBLP_MAX_RESULTS):
        raise ValueError(
            f"Cannot request more than {DBLP_MAX_RESULTS} results (got {max_results})"
            )

    if not (0 < max_completions <= DBLP_MAX_COMPLETIONS):
        raise ValueError(
            f"Cannot request more than {DBLP_MAX_COMPLETIONS} completions "
            f"(got {max_completions})")

    if output_format not in DBLP_FORMATS:
        raise ValueError(
            f"Unsupported format: '{output_format}' (expected {DBLP_FORMATS})"
            )

    url = DBLP_API_ENDPOINTS.get(api.lower())
    if url is None:
        raise ValueError(f"Unknown API endpoint '{api}'")

    from papis.utils import get_session

    with get_session() as session:
        response = session.get(
            url,
            params={
                "q": query,
                "format": output_format,
                "h": str(max_results),
                "f": "0",
                "c": str(max_completions),
            })

    return response.content.decode()


def get_data(query: str = "", max_results: int = 30) -> list[dict[str, Any]]:
    import json
    response = json.loads(
        search(query=query, output_format="json", max_results=max_results)
        )
    result = response.get("result")
    hits = result["hits"].get("hit")

    if hits is None:
        logger.error("Could not retrieve results from DBLP. Error: '%s'.",
                     result["status"]["text"])
        return []

    from papis.document import keyconversion_to_data

    key_conversion = _get_dblp_key_conversion()
    return [keyconversion_to_data(key_conversion, hit["info"]) for hit in hits]


def is_valid_dblp_key(key: str) -> bool:
    # FIXME: Is there some documentation on the form of the keys? From a quick
    # skim, they seem to be of the form
    #   <venue type>/<venue id>/<document id>

    if not re.match(r"[^\/]+\/[^\/]+\/[^\/]+", key):
        return False

    from papis.utils import get_session

    with get_session() as session:
        response = session.get(DBLP_URL_FORMAT.format(uri=key))
        return response.ok
